package com.cctc.juc.source.Athread.threadlocal.AThreadLocal;

import java.lang.ref.WeakReference;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 面试题：
 * 1. 为什么 ThreadLocalMap 使用 ThreadLocal<?> 实例的弱引用对 Key 进行包装，而不是直接使用 Threadlocal<?> 实例作为 Key 呢？？？
 * 答案：避免内存泄露（不再用到的内存 [Key 和 Value]，没有及时释放），即 ThreadLocalMap 的 key 不再使用了，或者 key 为 null 了，但是 value 还在，这就造成了内存泄漏。
 * - [Key 内存回收] 由于 ThreadLocalMap 的 Key 使用的是 Threadlocal<?> 实例的弱引用，在下次 GC 发生时，就可以使那些没有被其他任何运行（方法栈帧）工作线程（强引用）指向、仅被 Key 所（弱引用）指向的 ThreadLocal<?> 实例能被顺利回收。
 * - [Value 内存回收] 在 ThreadLocal 被回收之后，其 Entry 中的 Key 变为 null。
 * --> 1. 探测式回收。后续只要存在一个针对任何 ThreadLocal 的 get() 、 set() 或 remove() 操作时，ThreadLocalMap 都会主动清除这些 Key 为 null 的 Entry 条目，完成相应 Value 内存的释放。
 * --> 2. 启发式（并不是每次操作都进行，而是基于一定的条件和概率）回收。在 ThreadLocalMap 的 set() 方法中，有一个阈值（默认为 ThreadLocalMap.Entry 数组长度的 1/4），当执行完探测式回收后，剩余的 Entry 数量大于这个阈值时，就会触发一次启发式清理操作（执行 do-while 循环检查清理过期 key，结束条件是连续 n 次未发现过期 key 就结束循环）。
 * --> 3. 主动调用 remove() 方法手动回收。在使用线程池的场景中，因为线程池中的线程通常是长期存在的，它们的 ThreadLocal 变量也不会自动清理，很有可能导致内存泄漏。因此，在我们使用完 ThreadLocal 后显式调用 remove()方法，以确保不再需要的值能够被及时回收，key 和 value 都同时清理，一锅端，这样可以避免潜在的内存泄漏问题，并减少垃圾回收的压力。
 * 2. ThreadLocalMap 问什么要使用 {开放地址法 -> 线性探测}，而不是 {链地址法} 解决哈希冲突？？？
 * 答案：避免内存泄露（不再用到的内存 [Key 和 Value]，没有及时释放），即 ThreadLocalMap 的 key 不再使用了，或者 key 为 null 了，但是 value 还在，这就造成了内存泄漏。
 * 因为 ThreadLocalMap 为了避免内存泄露，使用的是 ThreadLocal<?> 实例的弱引用作为 Entry 条目的关键字 key，当线程终止后，其内部指向的 ThreadLocal<?> 实例的强引用（threadLocals）也销毁了，此时只剩下 ThreadLocalMap 为该线程生成的 Entry 条目的关键字 key 弱引用指向 ThreadLocal<?> 实例，在下次 GC 时该弱引用将会被回收，即关键字 key 变为 null，最后其所在的 Entry 将会被
 * ThreadLocalMap 清除，线程的局部变量到此才被完全清理、所占空间也被顺利回收。
 * 试想一下，如果 ThreadLocalMap 采用的是 {链地址法}，当发生冲突时，将多个线程的局部变量以链接的方式放到同一个槽位内，如果其中某个线程终止了，由于其他线程仍然强引用指向 ThreadLocal<?> 实例，导致其所在槽内的局部变量无法及时得到清理，所占空间也得不到回收，从而引发内存泄露问题。
 */
public class TThreadLocal_AAAAA<T> {
    /**
     * 1.（核心）哈希函数：乘法（倍数）哈希。
     * 目的：获取每个线程所持有的 ThreadLocal 线程局部变量的哈希码。
     * 具体实现：下一个线程通过 nextHashCode() 方法获取到的 threadLocalHashCode 散列值永远是前一个线程获取到的 2 倍。
     * 即将线程局部变量 ID 转化为接近最优分布的 [乘法哈希值]，以减少哈希冲突。
     */
    private final int threadLocalHashCode = nextHashCode();
    private static AtomicInteger nextHashCode = new AtomicInteger();
    private static final int HAS_INCREMENT = 0x61c88647;

    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HAS_INCREMENT);
    }

    /**
     * 2. [ThreadLocalMap 内部类]
     * 说明：ThreadLocalMap 是 ThreadLocal 的一个静态内部类，其没有继承 Map 的体系结构，而是自己实现了一套简单的 Map，其内部维护了一个 Entry<WeakReference<ThreadLocal<?>>, Object> 条目数组作为哈希表（使用 {开放寻址法 -> 线性探测} 来解决哈希冲突，比 HashMap 的链式寻址法简单）使用，ThreadLocal 线程局部变量的所有操作都是基于 ThreadLocalMap 实现的。
     */
    static class TLMap {
        static class Entry extends WeakReference<TThreadLocal_AAAAA<?>> {
            Object value;

            public Entry(TThreadLocal_AAAAA<?> key, Object value) {
                super(key);
                this.value = value;
            }
        }

        private Entry[] table;
        private int size;
        private int threshold;
        private static final int INITIAL_CAPACITY = 1 << 4;

        /**
         * 哈希冲突解决方案：{开放地址法 -> 线性探测（步长为 1）}。
         */

        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

        private static int prevIndex(int i, int len) {
            return ((i - 1 >= 0) ? i - 1 : len - 1);
        }

        /**
         * 核心方法：写（插入 | 更新）操作。
         * 核心（实现）逻辑：
         * 1）基于关键字 key 的散列值，定位到目标节点在 table 数组中的位置（下标），寻找目标节点；
         * 1.1）如果目标节点存在，则执行更新操作，然后直接返回；
         * 1.2）如果不存在，位置发生冲突，使用 {开放地址法 -> 线性探测} 寻找下一个可用位置（下标），执行插入操作；
         * 2）执行完插入操作后：
         * 2.1）调用 cleanSomeSlots() 方法，“启发式地” 扫描一些单元格，清除关键字 key = null 的（过时）元素。
         * 2.2）如果执行完清理操作后，数组大小（元素数量）还是达到了扩容阈值（threshold），调用 resize() 方法，执行扩容操作。
         *
         * @param key   关键字
         * @param value 元素值
         */
        private void set(TThreadLocal_AAAAA<?> key, Object value) {
            Entry[] tab = table;
            int n = tab.length;
            int index = key.threadLocalHashCode & (n - 1);
            for (Entry entry = tab[index]; entry != null; entry = tab[index = nextIndex(index, n)]) {
                TThreadLocal_AAAAA<?> k = entry.get();
                if (k == key) {
                    entry.value = value;
                    return;
                }
                if (k == null) {
                    replaceStaleEntry(key, value, index);
                    return;
                }
            }
            tab[index] = new Entry(key, value);
            ++size;
            if (!cleanSomeSlots(index, size) && size >= threshold) {
                rehash();
            }
        }

        /**
         * 核心方法：读操作。
         * 核心（实现）逻辑：
         * 1）基于关键字 key 的散列值，定位到目标节点在 table 数组中的位置（下标），获取目标节点。
         * - 如果目标节点存在，直接返回结果；
         * - 否则，遍历数组，查找目标节点，同时调用 expungeStaleEntry() 方法，清除关键字 key = null 的（过时）元素。
         *
         * @param key 关键字
         * @return 元素。
         */
        private Entry getEntry(TThreadLocal_AAAAA<?> key) {
            int index = key.threadLocalHashCode & (table.length - 1);
            Entry entry = table[index];
            if (entry != null && entry.get() == key) return entry;
            else return getEntryAfterMiss(key, index, entry);
        }

        private Entry getEntryAfterMiss(TThreadLocal_AAAAA<?> key, int index, Entry entry) {
            Entry[] tab = table;
            int n = tab.length;
            while (entry != null) {
                TThreadLocal_AAAAA<?> k = entry.get();
                if (k == key) return entry;
                if (k == null) expungeStaleEntry(index);
                else index = nextIndex(index, n);
                entry = tab[index];
            }
            return null;
        }

        /**
         * 核心方法：删除操作。
         * 核心（实现）逻辑：
         * 1）基于关键字 key 的散列值，定位到目标节点在 table 数组中的位置（下标），获取目标（删除）节点。
         * 2）删除目标节点；
         * 3）调用 expungeStaleEntry() 方法，清除关键字 key = null 的（过时）元素。
         *
         * @param key 关键字
         */
        private void remove(TThreadLocal_AAAAA<?> key) {
            Entry[] tab = table;
            int n = tab.length;
            int index = key.threadLocalHashCode & (n - 1);
            for (Entry entry = tab[index]; entry != null; entry = tab[index = nextIndex(index, n)]) {
                if (entry.get() == key) {
                    entry.clear();
                    expungeStaleEntry(index);
                    return;
                }
            }
        }

        /**
         * 清除所有过期的元素。
         * 1）清除当前过期元素；
         * 2）清除所有 key = null 的过期元素。
         */
        private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int n = tab.length;

            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            size--;

            Entry entry;
            int index;
            for (index = nextIndex(staleSlot, n); (entry = tab[index]) != null; index = nextIndex(index, staleSlot)) {
                TThreadLocal_AAAAA<?> k = entry.get();
                if (k == null) {
                    entry.value = null;
                    tab[index] = null;
                    size--;
                } else {
                    int kIndex = k.threadLocalHashCode & (n - 1);
                    if (kIndex != index) {
                        tab[index] = null;
                        while (tab[kIndex] != null) {
                            kIndex = nextIndex(kIndex, n);
                            tab[kIndex] = entry;
                        }
                    }
                }
            }
            return index;
        }

    }

    /**
     * [ThreadLocal 常用方法]
     * - public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier);
     * - public void set(T value);
     * - public T get();
     * - public void remove();
     */

    public void set(T value) {
        Thread target = Thread.currentThread();
        TLMap map = target.threadLocals;
        if (map != null) {
            map.set(this, value);
        } else {
            target.threadLocals = new TLMap(this, null);
        }
    }

    public T get() {
        Thread target = Thread.currentThread();
        TLMap map = target.threadLocals;
        if (map != null) {
            TLMap.Entry e = map.getEntry(this);
            if (e != null) {
                T result = (T) e.value;
                return result;
            }
        }
        return null;
    }

    public void remove() {
        Thread target = Thread.currentThread();
        TLMap map = target.threadLocals;
        if (map != null) map.remove(this);
    }

}
